import { useCallback } from 'use-memo-one'
import React, { ReactNode, useEffect, useRef, useState } from 'react'
import { BackHandler, NativeEventSubscription, Platform } from 'react-native'

import type {
  SharedProps,
  ModalStateListener,
  ModalEventListeners,
  ModalContextProvider,
  ModalStateSubscription,
  ModalStack as ModalStackType,
} from '../types'

import ModalStack from './ModalStack'
import ModalState from './ModalState'
import ModalContext from './ModalContext'

import { invariant, validateListener } from '../utils'

interface Props {
  children: ReactNode
  stack: ModalStackType<any>
}

/**
 * `<ModalProvider>` is the component you're going to use to wrap your whole application,
 * so it'll be able to display your modals on top of everything else, using React Context API.
 *
 * @prop { ModalStackType } `stack` - Modal stack object generated by `createModalStack()`
 *
 * @see https://colorfy-software.gitbook.io/react-native-modalfy/guides/stack#provider
 */
const ModalProvider = ({ children, stack }: Props) => {
  const backHandlerSubscription = useRef<NativeEventSubscription>()
  const modalStateSubscription = useRef<ModalStateSubscription<any>>()

  const modalEventListeners = useRef<ModalEventListeners>(new Set()).current

  const openModal: SharedProps<any>['openModal'] = (modalName, params, callback) => {
    const { currentModal } = ModalState.getState()

    if (!currentModal) {
      backHandlerSubscription.current = BackHandler.addEventListener('hardwareBackPress', ModalState.handleBackPress)
    }

    ModalState.openModal({ modalName, params, callback, isCalledOutsideOfContext: true })
  }

  const getParam: SharedProps<any>['getParam'] = (hash, paramName, defaultValue) =>
    ModalState.getParam(hash, paramName, defaultValue)

  const closeModal: SharedProps<any>['closeModal'] = stackItem => ModalState.closeModal(stackItem)

  const closeModals: SharedProps<any>['closeModals'] = modalName => ModalState.closeModals(modalName)

  const closeAllModals: SharedProps<any>['closeAllModals'] = () => ModalState.closeAllModals()

  const [contextValue, setContextValue] = useState<ModalContextProvider<any, any>>({
    stack,
    getParam,
    openModal,
    closeModal,
    closeModals,
    closeAllModals,
    currentModal: null,
  })

  const registerListener: SharedProps<any>['registerListener'] = useCallback(
    (hash, eventName, handler) => {
      validateListener('add', { eventName, handler })
      const newListener = {
        event: `${hash}_${eventName}`,
        handler,
      }

      modalEventListeners.add(newListener)

      return {
        remove: () => modalEventListeners.delete(newListener),
      }
    },
    [modalEventListeners],
  )

  const clearListeners: SharedProps<any>['clearListeners'] = useCallback(
    hash => {
      modalEventListeners.forEach(item => {
        if (item.event.includes(hash)) modalEventListeners.delete(item)
      })
    },
    [modalEventListeners],
  )

  const listener: ModalStateListener<any> = (modalState, error) => {
    if (modalState) {
      setContextValue({
        ...contextValue,
        currentModal: modalState.currentModal,
        stack: modalState.stack,
      })
    } else console.warn('Modalfy', error)
  }

  useEffect(() => {
    invariant(stack, 'You need to provide a `stack` prop to <ModalProvider>')

    ModalState.init<any>(() => ({
      currentModal: null,
      stack,
    }))

    modalStateSubscription.current = ModalState.subscribe(listener)

    return () => {
      backHandlerSubscription.current?.remove?.()
      modalStateSubscription.current?.unsubscribe?.()
    }
  }, [])

  useEffect(() => {
    // NOTE: Used to prevent scrolling on Web when the modal stack is opened.
    if (Platform.OS === 'web' && contextValue.stack.openedItems.size) {
      document.body.style.overflow = 'hidden'
      document.body.style.touchAction = 'none'
      document.body.style.overscrollBehavior = 'none'
    }

    return () => {
      if (Platform.OS === 'web') {
        document.body.style.overflow = 'auto'
        document.body.style.touchAction = 'auto'
        document.body.style.overscrollBehavior = 'auto'
      }
    }
  }, [contextValue.stack.openedItems.size])

  return (
    <ModalContext.Provider value={contextValue}>
      <>
        {children}
        <ModalStack
          {...contextValue}
          clearListeners={clearListeners}
          registerListener={registerListener}
          eventListeners={modalEventListeners}
          removeClosingAction={ModalState.removeClosingAction}
        />
      </>
    </ModalContext.Provider>
  )
}

export default ModalProvider
